package com.box.app.arithmetic

internal typealias OnError = (before: List<Argument>, argument: Argument, error: ArithmeticError) -> Unit

internal class Variables(private val arguments: List<Argument>) : Operable, Iterable<Argument> {
    @Throws(ArithmeticError::class)
    fun parse(variables: Map<String, Number>?, onError: OnError? = null): List<Number> {
        val numbers = mutableListOf<Number>()
        val before = mutableListOf<Argument>()
        for (argument in arguments) {
            try {
                when (argument) {
                    is Variable1 -> numbers.add(argument.parse(variables))
                    is Expression -> numbers.add(argument.call(variables))
                }
            } catch (e: ArithmeticError) {
                if (onError != null) {
                    onError(before.toList(), argument, e)
                } else {
                    throw e
                }
            }
            before.add(argument)
        }
        return numbers
    }

    override fun toDescribe(isTight: Boolean): String = toDescribe(isTight, null)

    fun toDescribe(isTight: Boolean, variables: Map<String, Number>? = null): String {
        return arguments.joinToString { it.toDescribe(isTight, variables) }
    }

    override fun iterator() = arguments.iterator()

    override fun toString() = toDescribe(true)

    companion object {
        @Throws(ArithmeticError::class)
        fun of(s: String, parser: Parser): Arithmetic {
            val arguments = mutableListOf<Argument>()
            var level = 0
            val buffer = StringBuilder()
            for (element in s) {
                if (Curve.left.toString() == element.toString()) {
                    level++
                } else if (Curve.right.toString() == element.toString()) {
                    level--
                } else if (element == ',' && level == 0) {
                    val string = buffer.trim { it <= ' ' }
                    if (string.isNotEmpty()) {
                        arguments.add(parser.parseExpression(string))
                    }
                    buffer.clear()
                    continue
                }
                buffer.append(element)
            }
            val string = buffer.trim { it <= ' ' }
            if (string.isNotEmpty()) {
                arguments.add(parser.parseExpression(string))
            }
            val first = arguments.singleOrNull()
            return if (first is Expression) {
                var depth = 0
                for (factor in first) {
                    if (factor === Curve.left) {
                        depth++
                    } else if (factor === Curve.right) {
                        depth--
                    }
                }
                if (depth == 0) {
                    first
                } else {
                    Variables(listOf(first))
                }
            } else {
                Variables(arguments.toList())
            }
        }
    }

}